/* 
 * Copyright 2014 Josef Hertl.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package caetus.pkcs11.session;

import caetus.modelChecking.ModelMaker;
import caetus.pkcs11.key.CipherEnum;
import caetus.pkcs11.key.Key;
import caetus.pkcs11.key.PrivateKey;
import caetus.pkcs11.key.PublicKey;
import caetus.pkcs11.key.SecretKey;
import caetus.pkcs11.template.Template;
import caetus.pkcs11.template.TemplateMaker;
import caetus.pkcs11.tokenCapabilities.TokenCapabilities;
import caetus.pkcs11.tokenCapabilities.TokenCapability;
import caetus.pkcs11.tokenCapabilities.TokenCapabilityGroup;
import caetus.pkcs11.tokenCapabilities.TokenCapabilityValue;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import sun.security.pkcs11.wrapper.CK_ATTRIBUTE;
import sun.security.pkcs11.wrapper.CK_MECHANISM;
import sun.security.pkcs11.wrapper.PKCS11;
import sun.security.pkcs11.wrapper.PKCS11Constants;
import sun.security.pkcs11.wrapper.PKCS11Exception;

/**
 * The class that wraps a session. The variables only have getters, since they
 * should never be set from outside.
 *
 * @author Josef Hertl
 */
public class Session {

    private long sessionHandle;
    private int slot;
    private PKCS11 pkcs11;
    private TokenCapabilities tokenCapabilities = new TokenCapabilities();
    private TemplateMaker templateMaker = new TemplateMaker();
    private SecretKey SK;
    private SecretKey LSK;
    private SecretKey ISK;
    private PublicKey PuK;
    private PublicKey LPuK;
    private PublicKey IPuK;
    private PrivateKey PrK;
    private PrivateKey LPrK;
    private PrivateKey IPrK;
    private OutputLogger logger;
    private Random random = new Random();
    private CommandLineOptions options;

    /**
     * Creates a session based on the slot. Call login afterwards to access most
     * of the functionality.
     *
     * @param slot The slot for the session. Should be 1 for the first session
     * created.
     */
    public Session(int slot, String filePath, CommandLineOptions options) throws IOException, PKCS11Exception {
        this.slot = slot;
        pkcs11 = PKCS11.getInstance(filePath, null, null, true);
        long sessionFlags = PKCS11Constants.CKF_SERIAL_SESSION | PKCS11Constants.CKF_RW_SESSION;
        sessionHandle = pkcs11.C_OpenSession(slot, sessionFlags, null, null);
        this.options = options;
        this.logger = new OutputLogger(options.isShowFullResults());
    }

    /**
     * Logs into the token as a USER. Required for the most functions.
     *
     * @param pin Given as an array of individual characters
     * @throws PKCS11Exception When the login fails
     */
    public void login(char[] pin) throws PKCS11Exception {
        pkcs11.C_Login(sessionHandle, PKCS11Constants.CKU_USER, pin);
    }

    public void closeAndLogout() throws PKCS11Exception {
        try {
            pkcs11.C_Logout(sessionHandle);
        } catch (PKCS11Exception ex) {
//            System.err.println("Logout failed " + ex.getMessage());
        }
        pkcs11.C_CloseSession(sessionHandle);
        if (options.isInform()) {
            System.out.println("Closing session.");
        }
    }

    public long getSessionHandle() {
        return sessionHandle;
    }

    public int getSlot() {
        return slot;
    }

    public PKCS11 getPkcs11() {
        return pkcs11;
    }

    /**
     * Used to run a set of thorough tests on the token in order to discover the
     * token's capabilities.
     */
    public void testToken() throws PKCS11Exception {
        testCanCreateObject();
        testKeyGeneration();

        LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
        prepareKey(LSK, null, null);
        LPrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
        LPuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, LPrK);
        prepareKey(LPuK, null, null);

        testSymmetricEncryptDecrypt();
        testAsymmetricEncryptionDecryption();
        testFunctionalityWrappingKeys();
        testFunctionalityChangingKeyAttributes();
        testForModelChecker();
        testBasicFunctions();
        if (!options.isOnlyTestStandard()) {
            testKnownAttacks();
        }

        if (!options.isNoPrintCmd()) {
            System.out.println("");
            System.out.println("");
            tokenCapabilities.printCapabilities();
            System.out.println("");
            System.out.println("");
            System.out.println(logger.getResultStrings());
        }
        try {
            PrintWriter writer = new PrintWriter("testOutput.txt");
            writer.print(logger.getResultStrings());
            writer.close();
            if (!options.isNoPrintCmd()) {
                System.out.println("Test output saved to testOutput.txt in the same folder as Caetus.jar.");
            }
        } catch (FileNotFoundException exception) {
            System.err.println("No output was created: " + exception.getMessage());
            exception.printStackTrace();
        }

        if (options.isPerformModelChecking()) {
            //prepares for the run and maybe even separately
            prepareModelChecking(options.isPrepareModelChecking());
        } else if (options.isPrepareModelChecking()) {
            //only runs preparation if just prepare is true
            prepareModelChecking(true);
        }
    }

    /**
     * Discovers whether the token supports inserting new keys via
     * C_CreateObject
     */
    private void testCanCreateObject() throws PKCS11Exception {
        if (options.isInform()) {
            System.out.println("Testing functionality - creating objects.");
        }
        //Symmetric section
        try { //first tries AES, then 3DES - if at least one works, the test is considered successful
            ISK = new SecretKey(sessionHandle, pkcs11, CipherEnum.AES);
            ISK.create(null);
            tokenCapabilities.setWorkingSymmetricCipher(CipherEnum.AES);
            tokenCapabilities.addCapability(TokenCapability.CREATE_SYMMETRIC, TokenCapabilityValue.WORKS_ACCORDINGLY);
        } catch (PKCS11Exception ex) {
            try {
                ISK = new SecretKey(sessionHandle, pkcs11, CipherEnum.DES3);
                ISK.create(null);
                tokenCapabilities.setWorkingSymmetricCipher(CipherEnum.DES3);
                tokenCapabilities.addCapability(TokenCapability.CREATE_SYMMETRIC, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception pex) {
                tokenCapabilities.addCapability(TokenCapability.CREATE_SYMMETRIC, TokenCapabilityValue.DOES_NOT_WORK);
            }
        }
        //Asymmetric section
        try {
            //only tries RSA
            IPrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
            IPuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, IPrK);
            IPuK.createKeyPair(null, null);
            tokenCapabilities.addCapability(TokenCapability.CREATE_ASYMMETRIC, TokenCapabilityValue.WORKS_ACCORDINGLY);
        } catch (PKCS11Exception ex) {
            tokenCapabilities.addCapability(TokenCapability.CREATE_ASYMMETRIC, TokenCapabilityValue.DOES_NOT_WORK);
        }
    }

    /**
     * Tests key generation for both symmetric and asymmetric keys.
     */
    private void testKeyGeneration() throws PKCS11Exception {
        if (options.isInform()) {
            System.out.println("Testing fucntionality - key generation.");
        }
        //symmetric section - first AES, then DES. If the cipher is already defined, it is used instead.
        if (tokenCapabilities.getWorkingSymmetricCipher() != null) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.generate(null);
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_KEY_GENERATION, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                try {
                    //There is a possibility that the device does not permit attribute conflicts - therefore we try only enc+dec
                    SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                    Template template = templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher());
                    template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                    SK.generate(template);
                    tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_KEY_GENERATION, TokenCapabilityValue.WORKS_ACCORDINGLY);
                } catch (PKCS11Exception pex) {
                    //In theory there may be possibility that the given cipher can create but cannot generate - that, however, is extremely unlikely.
                    tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_KEY_GENERATION, TokenCapabilityValue.DOES_NOT_WORK);
//                    System.err.println(tokenCapabilities.getWorkingSymmetricCipher() + " key generation failed: " + ex.getMessage());
                }
            }
        } else {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, CipherEnum.AES);
                SK.generate(null);
                tokenCapabilities.setWorkingSymmetricCipher(CipherEnum.AES);
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_KEY_GENERATION, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                try {
                    SK = new SecretKey(sessionHandle, pkcs11, CipherEnum.AES);
                    Template template = templateMaker.getSecretKeyEncryptDecryptTemplate(CipherEnum.AES);
                    SK.generate(template);
                    tokenCapabilities.setWorkingSymmetricCipher(CipherEnum.AES);
                    tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_KEY_GENERATION, TokenCapabilityValue.WORKS_ACCORDINGLY);
                } catch (PKCS11Exception pex) {
//                    System.err.println("AES key generation failed: " + pex.getMessage());
                    try {
                        SK = new SecretKey(sessionHandle, pkcs11, CipherEnum.DES3);
                        SK.generate(null);
                        tokenCapabilities.setWorkingSymmetricCipher(CipherEnum.DES3);
                        tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_KEY_GENERATION, TokenCapabilityValue.WORKS_ACCORDINGLY);
                    } catch (PKCS11Exception exception) {
                        try {
                            SK = new SecretKey(sessionHandle, pkcs11, CipherEnum.DES3);
                            Template template = templateMaker.getSecretKeyEncryptDecryptTemplate(CipherEnum.DES3);
                            SK.generate(template);
                            tokenCapabilities.setWorkingSymmetricCipher(CipherEnum.DES3);
                            tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_KEY_GENERATION, TokenCapabilityValue.WORKS_ACCORDINGLY);
                        } catch (PKCS11Exception pkcse) {
//                            System.err.println("3DES key generation failed: " + pkcse.getMessage());
                            tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_KEY_GENERATION, TokenCapabilityValue.DOES_NOT_WORK);
                        }
                    }
                }
            }
        }
        //asymmetric section
        try {
            PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
            PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
            PuK.generateKeyPair(null, null);
            tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_KEY_GENERATION, TokenCapabilityValue.WORKS_ACCORDINGLY);
        } catch (PKCS11Exception ex) {
//            System.err.println("RSA key pair generation failed: " + ex.getMessage());
            tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_KEY_GENERATION, TokenCapabilityValue.DOES_NOT_WORK);
        }
    }

    /**
     * Tests whether the token is capable of performing symmetric cryptography.
     * Uses the previously established working symmetric cipher to perform the
     * tests.
     */
    private void testSymmetricEncryptDecrypt() {
        if (options.isInform()) {
            System.out.println("Testing symmetric cryptography.");
        }
        try {
            BigInteger bigOne = BigInteger.ONE;
            byte[] encryptionTestInput = bigOne.toByteArray();
            byte[] encrypted = SK.encrypt(encryptionTestInput);
            byte[] decrypted = SK.decrypt(encrypted);
            //now we want to compare the input and the result, but padding may have to be done first.
            byte[] resultComparator = new byte[decrypted.length];
            for (int i = 0; i < decrypted.length; i++) {
                if (encryptionTestInput.length > i) {
                    resultComparator[i] = encryptionTestInput[i];
                }
            }
            boolean decryptedIsTheSameAsTestInput = true;
            for (int i = 0; i < resultComparator.length; i++) {
                if (resultComparator[i] != decrypted[i]) {
                    decryptedIsTheSameAsTestInput = false;
                }
            }
            if (decryptedIsTheSameAsTestInput) {
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_ENCRYPTION_DECRYPTION, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } else {
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_ENCRYPTION_DECRYPTION, TokenCapabilityValue.DOES_NOT_WORK);
                System.err.println("Asymmetric encryption input and decryption output do not match!");
            }
        } catch (PKCS11Exception ex) {
//            System.err.println("The symmetric encryption test failed: " + ex.getMessage());
            tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_ENCRYPTION_DECRYPTION, TokenCapabilityValue.DOES_NOT_WORK);
        } catch (IllegalStateException ex) {
//            System.err.println("Symmetric encryption test failed, symmetric cryptography not supported.");
            tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_ENCRYPTION_DECRYPTION, TokenCapabilityValue.DOES_NOT_WORK);
        }
    }

    /**
     * Attempts encryption with PuK and then decryption with PrK
     */
    private void testAsymmetricEncryptionDecryption() {
        if (options.isInform()) {
            System.out.println("Testing asymmetric cryptography.");
        }

        BigInteger bigOne = BigInteger.ONE;
        try {
            byte[] encryptionTestInput = bigOne.toByteArray();
            byte[] encrypted = PuK.encrypt(encryptionTestInput);
            byte[] decrypted = PrK.decrypt(encrypted);
            //now we want to compare the input and the result, but padding may have to be done first.
            byte[] resultComparator = new byte[decrypted.length];
            for (int i = 0; i < decrypted.length; i++) {
                if (encryptionTestInput.length > i) {
                    resultComparator[i] = encryptionTestInput[i];
                }
            }
            boolean decryptedIsTheSameAsTestInput = true;
            for (int i = 0; i < resultComparator.length; i++) {
                if (resultComparator[i] != decrypted[i]) {
                    decryptedIsTheSameAsTestInput = false;
                }
            }
            if (decryptedIsTheSameAsTestInput) {
                tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_ENCRYPTION_DECRYPTION, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } else {
                tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_ENCRYPTION_DECRYPTION, TokenCapabilityValue.DOES_NOT_WORK);
            }
        } catch (PKCS11Exception ex) {
//            System.err.println("The asymmetric encryption test failed: " + ex.getMessage());
        }
    }

    private void testFunctionalityWrappingKeys() {
        if (options.isInform()) {
            System.out.println("Testing functionality - wrapping keys.");
        }
        //wrapping LSK by SK
        try {
            byte[] wrappedLSK = SK.wrapKey(LSK);
            Template unWrappingTemplate = new Template();
            unWrappingTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
            Key unwrapped = SK.unwrapSecretKey(wrappedLSK, unWrappingTemplate, tokenCapabilities.getWorkingSymmetricCipher());
            tokenCapabilities.addCapability(TokenCapability.WRAPPING_SYMMETRIC_BY_SYMMETRIC, TokenCapabilityValue.WORKS_ACCORDINGLY);
        } catch (PKCS11Exception ex) {
            try {
                byte[] wrappedLSK = SK.wrapKey(LSK);
                Template unWrappingTemplate = new Template();
                unWrappingTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                unWrappingTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                Key unwrapped = SK.unwrapSecretKey(wrappedLSK, unWrappingTemplate, tokenCapabilities.getWorkingSymmetricCipher());
//                System.err.println("Sym/sym wrapping works, but requires CKA_CLASS to be defined in the unwrapping template, unlike specified in the standard. (" + ex.getMessage() + ")");
                tokenCapabilities.addCapability(TokenCapability.WRAPPING_SYMMETRIC_BY_SYMMETRIC, TokenCapabilityValue.WORKS_NON_STANDARDLY);
            } catch (PKCS11Exception pex) {
                tokenCapabilities.addCapability(TokenCapability.WRAPPING_SYMMETRIC_BY_SYMMETRIC, TokenCapabilityValue.DOES_NOT_WORK);
//                System.err.println("Sym/sym wrap/unwrap test failed: " + ex.getMessage());
            }
        }
        //wrapping LPrK by SK
        try {
            byte[] wrappedLPrK = SK.wrapKey(LPrK);
            Template unWrappingTemplate = new Template();
            unWrappingTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, CipherEnum.RSA.getKeyType()));
            Key unwrapped = SK.unwrapSecretKey(wrappedLPrK, unWrappingTemplate, CipherEnum.RSA);
            tokenCapabilities.addCapability(TokenCapability.WRAPPING_ASYMMETRIC_BY_SYMMETRIC, TokenCapabilityValue.WORKS_ACCORDINGLY);
        } catch (PKCS11Exception ex) {
            try {
                byte[] wrappedLPrK = SK.wrapKey(LPrK);
                Template unWrappingTemplate = new Template();
                unWrappingTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, CipherEnum.RSA.getKeyType()));
                unWrappingTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_PRIVATE_KEY));
                Key unwrapped = SK.unwrapSecretKey(wrappedLPrK, unWrappingTemplate, CipherEnum.RSA);
//                System.err.println("Asym by sym wrapping works, but requires CKA_CLASS to be defined in the unwrapping template, unlike specified in the standard. (" + ex.getMessage() + ")");
                tokenCapabilities.addCapability(TokenCapability.WRAPPING_ASYMMETRIC_BY_SYMMETRIC, TokenCapabilityValue.WORKS_NON_STANDARDLY);
            } catch (PKCS11Exception pex) {
                tokenCapabilities.addCapability(TokenCapability.WRAPPING_ASYMMETRIC_BY_SYMMETRIC, TokenCapabilityValue.DOES_NOT_WORK);
//                System.err.println("Asym by sym wrap/unwrap test failed: " + ex.getMessage());
            }
        }
        //wrapping LSK by PuK
        try {
            byte[] wrappedLSK = PuK.wrapKey(LSK);
            Template unWrappingTemplate = new Template();
            unWrappingTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
            Key unwrapped = PrK.unwrapSecretKey(wrappedLSK, unWrappingTemplate, tokenCapabilities.getWorkingSymmetricCipher());
            tokenCapabilities.addCapability(TokenCapability.WRAPPING_SYMMETRIC_BY_ASYMMETRIC, TokenCapabilityValue.WORKS_ACCORDINGLY);
        } catch (PKCS11Exception ex) {
            try {
                byte[] wrappedLSK = PuK.wrapKey(LSK);
                Template unWrappingTemplate = new Template();
                unWrappingTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                unWrappingTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                Key unwrapped = PrK.unwrapSecretKey(wrappedLSK, unWrappingTemplate, tokenCapabilities.getWorkingSymmetricCipher());
//                System.err.println("Sym by asym wrapping works, but requires CKA_CLASS to be defined in the unwrapping template, unlike specified in the standard. (" + ex.getMessage() + ")");
                tokenCapabilities.addCapability(TokenCapability.WRAPPING_SYMMETRIC_BY_ASYMMETRIC, TokenCapabilityValue.WORKS_NON_STANDARDLY);
            } catch (PKCS11Exception pex) {
                tokenCapabilities.addCapability(TokenCapability.WRAPPING_SYMMETRIC_BY_ASYMMETRIC, TokenCapabilityValue.DOES_NOT_WORK);
//                System.err.println("Sym by asym wrap/unwrap test failed: " + ex.getMessage());
            }
        }
    }

    /**
     * Tries various changing of attributes for both asymmetric and symmetric
     * keys.
     */
    private void testFunctionalityChangingKeyAttributes() {
        if (options.isInform()) {
            System.out.println("Testing functionality - key attribute changing.");
        }
        try {
            //merely attempts to change Wrap (randomly chosen attribute) to false
            SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_WRAP, false));
            CK_ATTRIBUTE result = SK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_WRAP));
            if (!result.getBoolean()) {
                //now we can test setting from sensitive to nonsensitive (should not work)
                try {
                    SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, false));
                    //if the exception was not thrown
                    tokenCapabilities.addCapability(TokenCapability.CHANGE_KEY_ATTRIBUTES, TokenCapabilityValue.WORKS_NON_STANDARDLY);
                } catch (PKCS11Exception ex) {
                    //OK, now to test nonextractable to extractable (also should not work) - first sets extractable to false
                    try {
                        SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                        SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, true));
                        tokenCapabilities.addCapability(TokenCapability.CHANGE_KEY_ATTRIBUTES, TokenCapabilityValue.WORKS_NON_STANDARDLY);
                    } catch (PKCS11Exception pkex) {
                        //OK, now to test nonextractable to extractable (also should not work)
                        tokenCapabilities.addCapability(TokenCapability.CHANGE_KEY_ATTRIBUTES, TokenCapabilityValue.WORKS_ACCORDINGLY);
                    }
                }
            } else {
                tokenCapabilities.addCapability(TokenCapability.CHANGE_KEY_ATTRIBUTES, TokenCapabilityValue.DOES_NOT_WORK);
            }
        } catch (PKCS11Exception pex) {
//            System.err.println("Changing attribute test failed: " + pex.getMessage());
            tokenCapabilities.addCapability(TokenCapability.CHANGE_KEY_ATTRIBUTES, TokenCapabilityValue.DOES_NOT_WORK);
        }
        //test asymmetric if symmetric fails
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.CHANGING_KEY_ATTRIBUTES)) {
            try {
                PuK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_ID, "test"));
                tokenCapabilities.addCapability(TokenCapability.CHANGE_KEY_ATTRIBUTES, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                Logger.getLogger(Session.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * Initializes the key - either by generation or by creating. If the key is
     * public, it also initializes its private counterpart. The key object must
     * be created before!
     *
     * @param key The key to initialize. Public key in the case of asymmetric
     * crypto.
     * @param template The template passed to key (secret or public)
     * initialization. May be null in all cases.
     * @param privTemplate The template that is passed to private key, if any.
     * May be null in all cases or will be ignored if the key is secret.
     */
    private void prepareKey(Key key, Template template, Template privTemplate) throws PKCS11Exception {
        if (key instanceof SecretKey) {
            if (template != null) {
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 24));
            }
            if (tokenCapabilities.capabilityWorking(TokenCapability.SYMMETRIC_KEY_GENERATION)) {
                ((SecretKey) key).generate(template);
            } else if (tokenCapabilities.capabilityWorking(TokenCapability.CREATE_SYMMETRIC)) {
                ((SecretKey) key).create(template);
            }
        } else if (key instanceof PublicKey) {
            if (tokenCapabilities.capabilityWorking(TokenCapability.ASYMMETRIC_KEY_GENERATION)) {
                ((PublicKey) key).generateKeyPair(template, privTemplate);
            } else if (tokenCapabilities.capabilityWorking(TokenCapability.CREATE_ASYMMETRIC)) {
                ((PublicKey) key).createKeyPair(template, privTemplate);
            }
        }
    }

    /**
     * Performs basic tests for the functions.
     */
    private void testBasicFunctions() throws PKCS11Exception {
        try {
            testCreateObject();
            testCopyObject();
            testGetAttributeValue();
            testSetAttributeValue();
            testGenerateKey();
            testWrapKey();
            testUnwrapKey();
            testDeriveKey();
            testEncrypt();
            testDecrypt();
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(Session.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(Session.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Performs tests of CreateObject function, both symmetric and asymmetric.
     */
    private void testCreateObject() throws PKCS11Exception, NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing CreateObject.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            byte[] randomValue = new byte[24]; //we can safely use 24-byte long keys, since both DES3 and AES permit 24 byte long keys.
            random.nextBytes(randomValue);
            try {
                Template template = templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher());
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE, randomValue));
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.create(template);
                logger.addSuccess(true, "CreateObject - required template");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "CreateObject - require template", 1, "CreateObject does not work as specified in standard - key value, CKA_CLASS and CKA_KEY_TYPE should suffice.\n" + ex.getMessage());
            }
            try {
                Template template = new Template();
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE, randomValue));
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.create(template);
                logger.addFailureWithComment(true, "CreateObject - bad template without CLASS", 1, "CreateObject works even when CKA_CLASS is not specified.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "CreateObject - bad template without CLASS");
            }
            try {
                Template template = new Template();
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE, randomValue));
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.create(template);
                logger.addFailureWithComment(true, "CreateObject - bad template without KEY_TYPE", 1, "CreateObject works even when CKA_KEY_TYPE is not specified.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "CreateObject - bad template without KEY_TYPE");
            }
            try {
                Template template = new Template();
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.create(template);
                logger.addFailureWithComment(true, "CreateObject - bad template without key value", 1, "CreateObject works even when key value is not specified.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "CreateObject - bad template without key value");
            }
            try {
                Template template = templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher());
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 666));
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.create(template);
                logger.addFailureWithComment(true, "CreateObject - bad template with VALUE_LEN", 1, "CreateObject works even when VALUE_LEN is specified.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "CreateObject - bad template with VALUE_LEN");
            }
            try {
                Template template = templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher());
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE, randomValue));
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.create(template);
                boolean allFalse = true;
                if (SK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_ALWAYS_SENSITIVE))) {
                    allFalse = false;
                    logger.addFailureWithComment(true, "CreateObject - correctly set attributes to false on creation (ALWAYS_SENSITIVE)", 2, "ALWAYS_SENSITIVE should always be false after using CreateObject.");
                }
                if (SK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_NEVER_EXTRACTABLE))) {
                    allFalse = false;
                    logger.addFailureWithComment(true, "CreateObject - correctly set attributes to false on creation (NEVER_EXTRACTABLE)", 2, "NEVER_EXTRACTABLE should always be false after using CreateObject.");
                }
                if (SK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    allFalse = false;
                    logger.addFailureWithComment(true, "CreateObject - correctly set attributes to false on creation (LOCAL)", 2, "LOCAL should always be false after using CreateObject.");
                }
                if (allFalse) {
                    logger.addSuccess(true, "CreateObject - correctly set attributes to false on creation (LOCAL, ALWAYS_SENSITIVE, NEVER_EXTRACTABLE)");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "CreateObject - correctly set attributes to false on creation (LOCAL, ALWAYS_SENSITIVE, NEVER_EXTRACTABLE)", 2, "Test could not be performed, because key creation does not work correctly.\n" + ex.getMessage());
            }
        }
        //start of asymmetric section
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                Template privTemplate = templateMaker.getBasicRSAPrivateKeyTemplate(true, true, true, true);
                Template pubTemplate = templateMaker.getBasicRSAPublicKeyTemplate(true, true);
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.createKeyPair(pubTemplate, privTemplate);
                logger.addSuccess(false, "CreateObject - required template");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "CreateObject - require template", 1, "CreateObject does not work as specified in standard - key value, CKA_CLASS and CKA_KEY_TYPE should suffice.\n" + ex.getMessage());
            }
            try {
                Template privTemplate = templateMaker.getBasicRSAPrivateKeyValueTemplate();
                Template pubTemplate = templateMaker.getBasicRSAPublicKeyValueTemplate();
                privTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_RSA));
                pubTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_RSA));
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.createKeyPair(pubTemplate, privTemplate);
                logger.addFailureWithComment(false, "CreateObject - bad template without CLASS", 1, "CreateObject works even when CKA_CLASS is not specified.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "CreateObject - bad template without CLASS");
            }
            try {
                Template privTemplate = templateMaker.getBasicRSAPrivateKeyValueTemplate();
                Template pubTemplate = templateMaker.getBasicRSAPublicKeyValueTemplate();
                privTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_PRIVATE_KEY));
                pubTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_PUBLIC_KEY));
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.createKeyPair(pubTemplate, privTemplate);
                logger.addFailureWithComment(false, "CreateObject - bad template without KEY TYPE", 1, "CreateObject works even when CKA_KEY_TYPE is not specified.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "CreateObject - bad template without KEY TYPE");
            }
            try {
                Template privTemplate = new Template();
                Template pubTemplate = new Template();
                privTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_RSA));
                pubTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_RSA));
                privTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_PRIVATE_KEY));
                pubTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_PUBLIC_KEY));
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.createKeyPair(pubTemplate, privTemplate);
                logger.addFailureWithComment(false, "CreateObject - bad template without key value", 1, "CreateObject works even when key value is not specified.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "CreateObject - bad template without key value");
            }
            try {
                Template privTemplate = templateMaker.getBasicRSAPrivateKeyTemplate(true, true, true, true);
                Template pubTemplate = templateMaker.getBasicRSAPublicKeyTemplate(true, true);
                pubTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 666));
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.createKeyPair(pubTemplate, privTemplate);
                logger.addFailureWithComment(false, "CreateObject - bad template with MODULUS_BITS", 1, "CreateObject works even when MODULUS_BITS is specified.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "CreateObject - bad template with MODULUS_BITS");
            }
            try {
                Template privTemplate = templateMaker.getBasicRSAPrivateKeyTemplate(true, true, true, true);
                Template pubTemplate = templateMaker.getBasicRSAPublicKeyTemplate(true, true);
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.createKeyPair(pubTemplate, privTemplate);
                boolean allFalse = true;
                if (PrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_ALWAYS_SENSITIVE))) {
                    allFalse = false;
                    logger.addFailureWithComment(false, "CreateObject - correctly set attributes to false on creation (ALWAYS_SENSITIVE)", 2, "ALWAYS_SENSITIVE should always be false after using CreateObject.");
                }
                if (PrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_NEVER_EXTRACTABLE))) {
                    allFalse = false;
                    logger.addFailureWithComment(false, "CreateObject - correctly set attributes to false on creation (NEVER_EXTRACTABLE)", 2, "NEVER_EXTRACTABLE should always be false after using CreateObject.");
                }
                if (PrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    allFalse = false;
                    logger.addFailureWithComment(false, "CreateObject - correctly set attributes to false on creation (LOCAL - private key)", 2, "LOCAL should always be false after using CreateObject.");
                }
                if (PuK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    allFalse = false;
                    logger.addFailureWithComment(false, "CreateObject - correctly set attributes to false on creation (LOCAL - public key)", 2, "LOCAL should always be false after using CreateObject.");
                }
                if (allFalse) {
                    logger.addSuccess(false, "CreateObject - correctly set attributes to false on creation (LOCAL, ALWAYS_SENSITIVE, NEVER_EXTRACTABLE)");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "CreateObject - correctly set attributes to false on creation (LOCAL, ALWAYS_SENSITIVE, NEVER_EXTRACTABLE)", 2, "Test could not be performed, because key creation does not work correctly.\n" + ex.getMessage());
            }
        }
    }

    /**
     * Performs thorough tests of CopyObject function.
     */
    private void testCopyObject() throws PKCS11Exception, NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing CopyObject.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, false, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                Template copyTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, true));
                LSK = (SecretKey) SK.copyObject(copyTemplate);
                logger.addFailureWithComment(true, "CopyObject - change EXTRACTABLE", 3, "EXTRACTABLE should not be possible to set to TRUE in the template of CopyObject when the original EXTRACTABLE = false");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "CopyObject - change EXTRACTABLE");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, true, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                Template copyTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, false));
                LSK = (SecretKey) SK.copyObject(copyTemplate);
                logger.addFailureWithComment(true, "CopyObject - change SENSITIVE", 4, "SENSITIVE should not be possible to set to FALSE in the template of CopyObject when the original SENSITIVE = TRUE");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "CopyObject - change SENSITIVE");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, false, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                Template copyTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "irrelevant")); //just add some meaningless attribute so that the template is not empty
                LSK = (SecretKey) SK.copyObject(copyTemplate);
                boolean allCorrect = true;
                if (!LSK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_ALWAYS_SENSITIVE))) {
                    allCorrect = false;
                    logger.addFailureWithComment(true, "CopyObject - ALWAYS_SENSITIVE same as original.", 2, "ALWAYS_SENSITIVE was incorrectly set to FALSE during CopyObject.");
                }
                if (!LSK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_NEVER_EXTRACTABLE))) {
                    allCorrect = false;
                    logger.addFailureWithComment(true, "CopyObject - NEVER_EXTRACTABLE same as original.", 2, "NEVER_EXTRACTABLE was incorrectly set to FALSE during CopyObject.");
                }
                if (!LSK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    allCorrect = false;
                    logger.addFailureWithComment(true, "CopyObject - LOCAL same as original.", 2, "LOCAL was incorrectly set to FALSE during CopyObject.");
                }
                if (allCorrect) {
                    logger.addSuccess(true, "CopyObject - NEVER_EXTRACTABLE, ALWAYS_SENSITIVE, LOCAL same as original when original = TRUE");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "CopyObject - NEVER_EXTRACTABLE, ALWAYS_SENSITIVE, LOCAL same as original when original = TRUE", 2, "Test did not run properly, no conclusive results available. " + ex.getMessage());
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.create(null); //should create new key with ALWAYS_SENSITIVE, NEVER_EXTRACTABLE and LOCAL all set to FALSE
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "irrelevant")); //just add some meaningless attribute so that the template is not empty
                LSK = (SecretKey) SK.copyObject(template);
                boolean allCorrect = true;
                if (LSK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_ALWAYS_SENSITIVE))) {
                    allCorrect = false;
                    logger.addFailureWithComment(true, "CopyObject - ALWAYS_SENSITIVE same as original.", 2, "ALWAYS_SENSITIVE was incorrectly set to TRUE during CopyObject.");
                }
                if (LSK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_NEVER_EXTRACTABLE))) {
                    allCorrect = false;
                    logger.addFailureWithComment(true, "CopyObject - NEVER_EXTRACTABLE same as original.", 2, "NEVER_EXTRACTABLE was incorrectly set to TRUE during CopyObject.");
                }
                if (LSK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    allCorrect = false;
                    logger.addFailureWithComment(true, "CopyObject - LOCAL same as original.", 2, "LOCAL was incorrectly set to TRUE during CopyObject.");
                }
                if (allCorrect) {
                    logger.addSuccess(true, "CopyObject - NEVER_EXTRACTABLE, ALWAYS_SENSITIVE, LOCAL same as original when original = FALSE");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "CopyObject - NEVER_EXTRACTABLE, ALWAYS_SENSITIVE, LOCAL same as original when original = FALSE", 2, "Test did not run properly, no conclusive results available.");
            }
        }
        //asymmetric section
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template privTemplate = templateMaker.getRSAPrivateKeyGenerationTemplate(true, false, true, true);
                PuK.generateKeyPair(null, privTemplate);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, true));
                LPrK = (PrivateKey) PrK.copyObject(template);
                logger.addFailureWithComment(false, "CopyObject - change EXTRACTABLE", 3, "EXTRACTABLE should not be possible to set to TRUE in the template of CopyObject when the original EXTRACTABLE = false");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "CopyObject - change EXTRACTABLE");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template privTemplate = templateMaker.getRSAPrivateKeyGenerationTemplate(true, true, true, true);
                PuK.generateKeyPair(null, privTemplate);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, false));
                LPrK = (PrivateKey) PrK.copyObject(template);
                logger.addFailureWithComment(false, "CopyObject - change SENSITIVE", 4, "SENSITIVE should not be possible to set to FALSE in the template of CopyObject when the original EXTRACTABLE = false");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "CopyObject - change SENSITIVE");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template privTemplate = templateMaker.getRSAPrivateKeyGenerationTemplate(true, false, true, true);
                PuK.generateKeyPair(null, privTemplate);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "irrelevant")); //just add some meaningless attribute so that the template is not empty
                LPrK = (PrivateKey) PrK.copyObject(template);
                boolean allCorrect = true;
                if (!LPrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_ALWAYS_SENSITIVE))) {
                    allCorrect = false;
                    logger.addFailureWithComment(false, "CopyObject - ALWAYS_SENSITIVE same as original.", 2, "ALWAYS_SENSITIVE was incorrectly set to FALSE during CopyObject.");
                }
                if (!LPrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_NEVER_EXTRACTABLE))) {
                    allCorrect = false;
                    logger.addFailureWithComment(false, "CopyObject - NEVER_EXTRACTABLE same as original.", 2, "NEVER_EXTRACTABLE was incorrectly set to FALSE during CopyObject.");
                }
                if (!LPrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    allCorrect = false;
                    logger.addFailureWithComment(false, "CopyObject - LOCAL same as original.", 2, "LOCAL was incorrectly set to FALSE during CopyObject.");
                }
                if (allCorrect) {
                    logger.addSuccess(false, "CopyObject - NEVER_EXTRACTABLE, ALWAYS_SENSITIVE, LOCAL same as original when original = TRUE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "CopyObject - NEVER_EXTRACTABLE, ALWAYS_SENSITIVE, LOCAL same as original when original = true.", 2, "Test did not run properly, no conclusive results available. " + ex.getMessage());
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template privTemplate = templateMaker.getBasicRSAPrivateKeyTemplate(false, true, true, true);
                PuK.createKeyPair(null, privTemplate);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "irrelevant")); //just add some meaningless attribute so that the template is not empty
                LPrK = (PrivateKey) PrK.copyObject(template);
                boolean allCorrect = true;
                if (LPrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_ALWAYS_SENSITIVE))) {
                    allCorrect = false;
                    logger.addFailureWithComment(false, "CopyObject - ALWAYS_SENSITIVE same as original.", 2, "ALWAYS_SENSITIVE was incorrectly set to TRUE during CopyObject.");
                }
                if (LPrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_NEVER_EXTRACTABLE))) {
                    allCorrect = false;
                    logger.addFailureWithComment(false, "CopyObject - NEVER_EXTRACTABLE same as original.", 2, "NEVER_EXTRACTABLE was incorrectly set to TRUE during CopyObject.");
                }
                if (LPrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    allCorrect = false;
                    logger.addFailureWithComment(false, "CopyObject - LOCAL same as original.", 2, "LOCAL was incorrectly set to TRUE during CopyObject.");
                }
                if (allCorrect) {
                    logger.addSuccess(false, "CopyObject - NEVER_EXTRACTABLE, ALWAYS_SENSITIVE, LOCAL same as original when original = FALSE");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "CopyObject - NEVER_EXTRACTABLE, ALWAYS_SENSITIVE, LOCAL same as original when original = FALSE", 2, "Test did not run properly, no conclusive results available. " + ex.getMessage());
            }
        }
    }

    /**
     * Runs tests on GetAttributeValue function.
     */
    private void testGetAttributeValue() throws PKCS11Exception, NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing GetAttributeValue.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, true, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                SK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE));
                logger.addFailureWithComment(true, "GetAttributeValue - key with SENSITIVE = TRUE", 4, "Key value was shown even though SENSITIVE = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "GetAttributeValue - key with SENSITIVE = TRUE");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), false, false, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                SK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE));
                logger.addFailureWithComment(true, "GetAttributeValue - key with EXTRACTABLE = FALSE", 4, "Key value was shown even though EXTRACTABLE = FALSE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "GetAttributeValue - key with EXTRACTABLE = FALSE");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), false, true, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                SK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE));
                logger.addSuccess(true, "GetAttributeValue - key with EXTRACTABLE = TRUE and SENSITIVE = FALSE");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "GetAttributeValue - key with EXTRACTABLE = TRUE and SENSITIVE = FALSE", 4, "Key value was not shown even though EXTRACTABLE = TRUE and SENSITIVE = FALSE.");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, false, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                SK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_ENCRYPT));
                SK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE));
                SK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE));
                SK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_DECRYPT));
                SK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_UNWRAP));
                SK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SIGN));
                logger.addSuccess(true, "GetAttributeValue - show other attributes");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "GetAttributeValue - show other attributes", 1, "Key with EXTRACTABLE = FALSE and SENSITIVE = TRUE does not show attributes it normally should, i.e. SENSITIVE, EXTRACTABLE or ENCRYPT...");
            }
        }
        //assymetric section
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template privTemplate = templateMaker.getRSAPrivateKeyGenerationTemplate(true, true, true, true);
                PuK.generateKeyPair(null, privTemplate);
                PrK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE));
                logger.addFailureWithComment(false, "GetAttributeValue - key with SENSITIVE = TRUE", 4, "Key value was shown even though SENSITIVE = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "GetAttributeValue - key with SENSITIVE = TRUE");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template privTemplate = templateMaker.getRSAPrivateKeyGenerationTemplate(false, false, true, true);
                PuK.generateKeyPair(null, privTemplate);
                PrK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE));
                logger.addFailureWithComment(false, "GetAttributeValue - key with EXTRACTABLE = FALSE", 4, "Key value was shown even though EXTRACTABLE = FALSE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "GetAttributeValue - key with EXTRACTABLE = FALSE");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template privTemplate = templateMaker.getRSAPrivateKeyGenerationTemplate(false, true, true, true);
                PuK.generateKeyPair(null, privTemplate);
                PrK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_PRIVATE_EXPONENT));
                logger.addSuccess(false, "GetAttributeValue - key with EXTRACTABLE = TRUE and SENSITIVE = FALSE");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "GetAttributeValue - key with EXTRACTABLE = TRUE and SENSITIVE = FALSE", 4, "Key value was not shown even though EXTRACTABLE = TRUE and SENSITIVE = FALSE. " + ex.getMessage());
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template privTemplate = templateMaker.getRSAPrivateKeyGenerationTemplate(true, false, true, true);
                PuK.generateKeyPair(null, privTemplate);
                PrK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE));
                PrK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE));
                PrK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_DECRYPT));
                PrK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_UNWRAP));
                logger.addSuccess(false, "GetAttributeValue - show other attributes");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "GetAttributeValue - show other attributes", 1, "Key with EXTRACTABLE = FALSE and SENSITIVE = TRUE does not show attributes it normally should, i.e. SENSITIVE, EXTRACTABLE or DECRYPT...");
            }
        }
    }

    private void testSetAttributeValue() throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing SetAttributeValue.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, true, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, false));
                logger.addFailureWithComment(true, "SetAttributeValue - key with SENSITIVE = TRUE", 4, "SENSITIVE = TRUE can be changed to FALSE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "SetAttributeValue - key with SENSITIVE = TRUE");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, false, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, true));
                logger.addFailureWithComment(true, "SetAttributeValue - key with EXTRACTABLE = FALSE", 3, "EXTRACTABLE = FALSE can be changed to TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "SetAttributeValue - key with EXTRACTABLE = FALSE");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), false, true, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, true));
                logger.addSuccess(true, "SetAttributeValue - key with SENSITIVE = FALSE");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "SetAttributeValue - key with SENSITIVE = FALSE", 1, "SENSITIVE = FALSE can not be changed to TRUE. " + ex.getMessage());
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), false, true, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                logger.addSuccess(true, "SetAttributeValue - key with EXTRACTABLE = TRUE");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "SetAttributeValue - key with EXTRACTABLE = TRUE", 1, "EXTRACTABLE = TRUE can not be changed to FALSE.");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), false, true, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, true));
                if (!SK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_ALWAYS_SENSITIVE))) {
                    logger.addSuccess(true, "SetAttributeValue - stable ALWAYS_SENSITIVE");
                } else {
                    logger.addFailureWithComment(true, "SetAttributeValue - stable ALWAYS_SENSITIVE", 2, "Setting SENSITIVE to TRUE incorrectly also changed ALWAYS_SENSITIVE to TRUE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "SetAttributeValue - stable ALWAYS_SENSITIVE", 2, "Test failed with no conclusive results.");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, true, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                if (!SK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_NEVER_EXTRACTABLE))) {
                    logger.addSuccess(true, "SetAttributeValue - stable NEVER_EXTRACTABLE");
                } else {
                    logger.addFailureWithComment(true, "SetAttributeValue - stable NEVER_EXTRACTABLE", 2, "Setting EXTRACTABLE to FALSE incorrectly also changed NEVER_EXTRACTABLE to TRUE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "SetAttributeValue - stable NEVER_EXTRACTABLE", 2, "Test failed with no conclusive results.");
            }
        }
        //asymmetric section
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template template = templateMaker.getRSAPrivateKeyGenerationTemplate(true, false, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                PuK.generateKeyPair(null, template);
                PrK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, false));
                logger.addFailureWithComment(false, "SetAttributeValue - key with SENSITIVE = TRUE", 4, "SENSITIVE = TRUE can be changed to FALSE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "SetAttributeValue - key with SENSITIVE = TRUE");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template template = templateMaker.getRSAPrivateKeyGenerationTemplate(true, false, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                PuK.generateKeyPair(null, template);
                PrK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, true));
                logger.addFailureWithComment(false, "SetAttributeValue - key with EXTRACTABLE = FALSE", 3, "EXTRACTABLE = FALSE can be changed to TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "SetAttributeValue - key with EXTRACTABLE = FALSE");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template template = templateMaker.getRSAPrivateKeyGenerationTemplate(false, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                PuK.generateKeyPair(null, template);
                PrK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, true));
                logger.addSuccess(false, "SetAttributeValue - key with SENSITIVE = FALSE");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "SetAttributeValue - key with SENSITIVE = FALSE", 1, "SENSITIVE = FALSE can not be changed to TRUE.");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template template = templateMaker.getRSAPrivateKeyGenerationTemplate(false, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                PuK.generateKeyPair(null, template);
                PrK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                logger.addSuccess(false, "SetAttributeValue - key with EXTRACTABLE = TRUE");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "SetAttributeValue - key with EXTRACTABLE = TRUE", 1, "EXTRACTABLE = TRUE can not be changed to FALSE.");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template template = templateMaker.getRSAPrivateKeyGenerationTemplate(false, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                PuK.generateKeyPair(null, template);
                PrK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, true));
                if (!PrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_ALWAYS_SENSITIVE))) {
                    logger.addSuccess(false, "SetAttributeValue - stable ALWAYS_SENSITIVE");
                } else {
                    logger.addFailureWithComment(false, "SetAttributeValue - stable ALWAYS_SENSITIVE", 2, "Setting SENSITIVE to TRUE incorrectly also changed ALWAYS_SENSITIVE to TRUE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "SetAttributeValue - stable ALWAYS_SENSITIVE", 2, "Test failed with no conclusive results.");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template template = templateMaker.getRSAPrivateKeyGenerationTemplate(true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                PuK.generateKeyPair(null, template);
                PrK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                if (!SK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_NEVER_EXTRACTABLE))) {
                    logger.addSuccess(false, "SetAttributeValue - stable NEVER_EXTRACTABLE");
                } else {
                    logger.addFailureWithComment(false, "SetAttributeValue - stable NEVER_EXTRACTABLE", 2, "Setting EXTRACTABLE to FALSE incorrectly also changed NEVER_EXTRACTABLE to TRUE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "SetAttributeValue - stable NEVER_EXTRACTABLE", 2, "Test failed with no conclusive results. " + ex.getMessage());
            }
        }
    }

    /**
     * Performs tests on GenerateKey function
     */
    private void testGenerateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing GenerateKey.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                Template template = new Template();
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.generate(template);
                logger.addSuccess(true, "GenerateKey - required template");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "GenerateKey - required template", 1, "GenerateKey does not work as specified in standard - VALUE_LEN should suffice.\n" + ex.getMessage());
            }
            try {
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninglessText"));
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.generate(template);
                logger.addFailureWithComment(true, "GenerateKey - bad template without KEY_LEN", 1, "GenerateKey works even when KEY_LEN is not specified.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "GenerateKey - bad template without KEY_LEN");
            }
            try {
                Template template = new Template();
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE, 666));
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.generate(template);

                logger.addFailureWithComment(true, "GenerateKey - bad template with key value", 3, "GenerateKey works even when key value is specified.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "GenerateKey - bad template with key value");
            }
            try {
                Template template = new Template();
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.generate(template);
                if (SK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    logger.addSuccess(true, "GenerateKey - GenerateKey - LOCAL set to TRUE");
                } else {
                    logger.addFailureWithComment(true, "GenerateKey - LOCAL set to TRUE", 2, "LOCAL attribute should be set to TRUE when a key is newly generated.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "GenerateKey - LOCAL set to TRUE", 2, "Test failed without a result." + ex.getMessage());
            }
        }
        //asymmetric section
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                Template template = new Template();
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                Template emptyTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "filler"));
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.generateKeyPair(template, emptyTemplate);
                logger.addSuccess(false, "GenerateKey - required template");
            } catch (PKCS11Exception ex) { //for some reason RSA generation also requires public exponent
                logger.addFailureWithComment(false, "GenerateKey - required template", 1, "GenerateKey does not work as specified in standard - MODULUS_BITS should suffice." + ex.getMessage());
            }
            try {
                Template template = new Template();
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "placeholder"));
                Template emptyTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "filler"));
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.generateKeyPair(template, emptyTemplate);
                logger.addFailureWithComment(false, "GenerateKey - bad template without MODULUS_BITS", 1, "Generate Key Pair works even when MODULUS_BITS is not specified.");
            } catch (PKCS11Exception ex) { //for some reason RSA generation also requires public exponent
                logger.addSuccess(false, "GenerateKey - bad template without MODULUS_BITS");
            }
            try {
                Template pubTemplate = templateMaker.getBasicRSAPublicKeyValueTemplate();
                pubTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                Template privTemplate = templateMaker.getBasicRSAPrivateKeyValueTemplate();
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.generateKeyPair(pubTemplate, privTemplate);
                if (pubTemplate.finishAndGetAttributes()[0] == PuK.getAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS))) {
                    logger.addFailureWithComment(false, "GenerateKey - bad template with key value", 5, "GenerateKeyPair works even when key value is specified in the templates and the value of the newly generated key is the same as the one specified in the template.");
                } else {
                    logger.addFailureWithComment(false, "GenerateKey - bad template with key value", 2, "GenerateKeyPair works even when key value is specified in the templates.");
                }
            } catch (PKCS11Exception ex) { //for some reason RSA generation also requires public exponent
                logger.addSuccess(false, "GenerateKey - bad template with key value");
            }
            try {
                Template template = new Template();
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                Template emptyTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "filler"));
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.generateKeyPair(template, emptyTemplate);
                if (PrK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    logger.addSuccess(false, "GenerateKey - GenerateKey - LOCAL set to TRUE");
                } else {
                    logger.addFailureWithComment(false, "GenerateKey - LOCAL set to TRUE", 2, "LOCAL attribute should be set to TRUE when a key is newly generated.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "GenerateKey - LOCAL set to TRUE", 2, "Test failed without a result." + ex.getMessage());
            }
        }
    }

    /**
     * Thoroughly tests WrapKey function.
     */
    private void testWrapKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing WrapKey.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                SK.wrapKey(LSK);
                logger.addSuccess(true, "Symmetric wraps symmetric");
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_WRAPPING_KEY_CAN_WRAP, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Symmetric wraps symmetric", 1, "Symmetric key cannot be used for wrapping of another symmetric key. " + ex.getMessage());
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                prepareKey(PuK, null, null);
                SK.wrapKey(PrK);
                logger.addSuccess(true, "Symmetric wraps private");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Symmetric wraps private", 1, "Symmetric key cannot be used for wrapping of another private key. " + ex.getMessage());
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                prepareKey(PuK, null, null);
                PuK.wrapKey(SK);
                logger.addSuccess(true, "Public wraps symmetric");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Public wraps symmetric", 1, "Public key cannot be used for wrapping of a symmetric key. " + ex.getMessage());
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                SK.wrapKey(LSK);
                logger.addFailureWithComment(true, "Wraps without WRAP", 3, "Key can be used to wrap even if it has WRAP = FALSE");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Wraps without WRAP");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, false, true, true, true, true), null);
                SK.wrapKey(LSK);
                logger.addFailureWithComment(true, "Wraps without EXPORTABLE", 3, "Key can be used to wrap even if the wrapped key has EXPORTABLE = FALSE");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Wraps without EXPORTABLE");
            }
        }
    }

    /**
     * Tests for UnwrapKey.
     */
    private void testUnwrapKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing UnwrapKey.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = SK.wrapKey(LSK);
                try {
                    Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                    template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                    SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                    logger.addSuccess(true, "Unwrap required template");
                    tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_WRAPPING_KEY_CAN_UNWRAP, TokenCapabilityValue.WORKS_ACCORDINGLY);
                } catch (PKCS11Exception pex) {
                    logger.addFailureWithComment(true, "Unwrap required template.", 1, "Unwrapping does not work with just the key type specified.");
                }
                try {
                    SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                    prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                    Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                    template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE, 666));
                    SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                    logger.addFailureWithComment(true, "Unwrap with bad template - including key value", 3, "Unwrapping works even when key value is specified.");
                } catch (PKCS11Exception pex) {
                    logger.addSuccess(true, "Unwrap with bad template - including key value");
                }
                try {
                    SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                    prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                    Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                    template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                    template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                    SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                    logger.addFailureWithComment(true, "Unwrap with bad template - including VALUE_LEN", 1, "Unwrapping works even when VALUE_LEN is specified.");
                } catch (PKCS11Exception pex) {
                    logger.addSuccess(true, "Unwrap with bad template - including VALUE_LEN");
                }
                try {
                    SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                    prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                    Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                    template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                    ISK = SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                    if (ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                        logger.addFailureWithComment(true, "Unwrap sets LOCAL to FALSE", 3, "Unwrapped key has LOCAL incorrectly set to TRUE.");
                    } else {
                        logger.addSuccess(true, "Unwrap sets LOCAL to FALSE");
                    }
                } catch (PKCS11Exception pex) {
                    logger.addFailureWithComment(true, "Unwrap sets LOCAL to FALSE", 3, "Function failed, no results. " + pex.getMessage());
                }
                try {
                    SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                    prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                    Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                    SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_UNWRAP, false));
                    template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                    SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                    logger.addFailureWithComment(true, "Unwrap with UNWRAP = FALSE", 3, "Unwrapping works even when UNWRAP = FALSE.");
                } catch (PKCS11Exception pex) {
                    logger.addSuccess(true, "Unwrap with UNWRAP = FALSE");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Unwrapping", 3, "UnwrapKey does not work at all, since there is a problem with wrapping. " + ex.getMessage());
            }
        }
        //assymetric attempt for model checker discovery
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template privTemplate = templateMaker.getRSAPrivateKeyGenerationTemplate(false, false, false, true);
                privTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                Template pubTemplate = templateMaker.getRSAPublicKeyGenerationTemplate(new BigInteger("257").toByteArray(), 512, false, true);
                PuK.generateKeyPair(pubTemplate, privTemplate);
                tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_WRAPPING_KEY_CAN_UNWRAP, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_WRAPPING_KEY_CAN_UNWRAP, TokenCapabilityValue.DOES_NOT_WORK);
            }
        }
    }

    /**
     * Tests for DeriveKey.
     */
    private void testDeriveKey() throws PKCS11Exception {
        if (options.isInform()) {
            System.out.println("Testing DeriveKey.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {//testing LOCAL with two key concatenation
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true)), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, null, null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_CONCATENATE_BASE_AND_KEY, LSK.getKeyHandle());
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                if (!ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    logger.addSuccess(true, "Derivation (key concatenation) has LOCAL set to FALSE");
                } else {
                    logger.addFailureWithComment(true, "Derivation (key concatenation) has LOCAL set to FALSE", 2, "LOCAL of the newly derived key is mistakingly set to TRUE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Derivation (key concatenation) has LOCAL set to FALSE", 2, "Key derivation failed. " + ex.getMessage());
            }
            try {//testing SENSITIVE with two key concatenation
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, true));
                prepareKey(SK, new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true)), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, null, null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_CONCATENATE_BASE_AND_KEY, LSK.getKeyHandle());
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                if (ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE))) {
                    logger.addSuccess(true, "Derivation (key concatenation) has SENSITIVE set to TRUE when only the first key is sensitive");
                } else {
                    logger.addFailureWithComment(true, "Derivation (key concatenation) has SENSITIVE set to TRUE when only the first key is sensitive", 4, "SENSITIVE of the newly derived key is mistakingly set to FALSE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Derivation (key concatenation) has SENSITIVE set to TRUE when only the first key is sensitive", 4, "Key derivation failed. " + ex.getMessage());
            }
            try {//testing SENSITIVE with two key concatenation (second key)
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, false));
                prepareKey(SK, template, null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, null, null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_CONCATENATE_BASE_AND_KEY, LSK.getKeyHandle());
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                if (ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE))) {
                    logger.addSuccess(true, "Derivation (key concatenation) has SENSITIVE set to TRUE when only the second key is sensitive");
                } else {
                    logger.addFailureWithComment(true, "Derivation (key concatenation) has SENSITIVE set to TRUE when only the second key is sensitive", 4, "SENSITIVE of the newly derived key is mistakingly set to FALSE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Derivation (key concatenation) has SENSITIVE set to TRUE when only the second key is sensitive", 4, "Key derivation failed. " + ex.getMessage());
            }
            try {//testing EXTRACTABLE with two key concatenation (first key)
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                prepareKey(SK, template, null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template templateB = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true));
                templateB.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, true));
                prepareKey(LSK, templateB, null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_CONCATENATE_BASE_AND_KEY, LSK.getKeyHandle());
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                if (!ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE))) {
                    logger.addSuccess(true, "Derivation (key concatenation) has EXTRACTABLE set to FALSE when only the first key is not extractable");
                } else {
                    logger.addFailureWithComment(true, "Derivation (key concatenation) has EXTRACTABLE set to FALSE when only the first key is not extractable", 3, "EXTRACTABLE of the newly derived key is mistakingly set to TRUE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Derivation (key concatenation) has EXTRACTABLE set to FALSE when only the first key is not extractable", 3, "Key derivation failed. " + ex.getMessage());
            }
            try {//testing EXTRACTABLE with two key concatenation (second key)
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, true));
                prepareKey(SK, template, null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template templateB = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true));
                templateB.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                prepareKey(LSK, templateB, null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_CONCATENATE_BASE_AND_KEY, LSK.getKeyHandle());
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                if (!ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE))) {
                    logger.addSuccess(true, "Derivation (key concatenation) has EXTRACTABLE set to FALSE when only the second key is not extractable");
                } else {
                    logger.addFailureWithComment(true, "Derivation (key concatenation) has EXTRACTABLE set to FALSE when only the second key is not extractable", 3, "EXTRACTABLE of the newly derived key is mistakingly set to TRUE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Derivation (key concatenation) has EXTRACTABLE set to FALSE when only the second key is not extractable", 3, "Key derivation failed. " + ex.getMessage());
            }
            try {//testing DERIVE = FALSE
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, false));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, true));
                prepareKey(SK, template, null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_EXTRACT_KEY_FROM_KEY, (long) 0); //this is a gambit that could potentially lead to catastrophic disaster, but seemingly works..
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 24));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                logger.addFailureWithComment(true, "Derivation (key extraction) cannot work with DERIVE = FALSE", 3, "Key derivation incorrectly works even though the key has DERIVE = FALSE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Derivation (key extraction) cannot work with DERIVE = FALSE");
            }
            try {//testing local with key extraction
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true)), null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_EXTRACT_KEY_FROM_KEY, (long) 0); //this is a gambit that could potentially lead to catastrophic disaster, but seemingly works..
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 24));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                if (!ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_LOCAL))) {
                    logger.addSuccess(true, "Derivation (key extraction) has LOCAL set to FALSE");
                } else {
                    logger.addFailureWithComment(true, "Derivation (key extraction) has LOCAL set to FALSE", 2, "LOCAL of the newly derived key is mistakingly set to TRUE.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Derivation (key extraction) has LOCAL set to FALSE", 2, "Key derivation failed. " + ex.getMessage());
            }
            try {//testing sensitive with key extraction
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, true));
                prepareKey(SK, template, null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_EXTRACT_KEY_FROM_KEY, (long) 0); //this is a gambit that could potentially lead to catastrophic disaster, but seemingly works..
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 24));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                if (ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE))) {
                    logger.addSuccess(true, "Derivation (key extraction) has SENSITIVE set to TRUE");
                } else {
                    logger.addFailureWithComment(true, "Derivation (key extraction) has SENSITIVE set to TRUE", 4, "SENSITIVE of the newly derived key is mistakingly set to FALSE, but the original key is sensitive.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Derivation (key extraction) has SENSITIVE set to TRUE", 4, "Key derivation failed. " + ex.getMessage());
            }
            try {//testing EXTRACTABLE with key extraction
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                prepareKey(SK, template, null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_EXTRACT_KEY_FROM_KEY, (long) 0); //this is a gambit that could potentially lead to catastrophic disaster, but seemingly works..
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 24));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                if (!ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE))) {
                    logger.addSuccess(true, "Derivation (key extraction) has EXTRACTABLE set to FALSE");
                } else {
                    logger.addFailureWithComment(true, "Derivation (key extraction) has EXTRACTABLE set to FALSE", 3, "EXTRACTABLE of the newly derived key is mistakingly set to TRUE, but the original key is not extractable.");
                }
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Derivation (key extraction) has EXTRACTABLE set to FALSE", 3, "Key derivation failed. " + ex.getMessage());
            }
        }
    }

    /**
     * Simple Encrypt function tests.
     */
    private void testEncrypt() throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing Encrypt.");
        }
        byte[] dataToEncrypt = BigInteger.TEN.toByteArray();
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                SK.encrypt(dataToEncrypt);
                logger.addFailureWithComment(true, "Key performs encryption only when ENCRYPT = TRUE", 2, "Encryption is possible even when the key has ENCRYPT = FALSE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Key performs encryption only when ENCRYPT = TRUE");
            }
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                prepareKey(PuK, templateMaker.getBasicRSAPublicKeyTemplate(false, false), null);
                PuK.encrypt(dataToEncrypt);
                logger.addFailureWithComment(false, "Key performs encryption only when ENCRYPT = TRUE", 2, "Encryption is possible even when the key has ENCRYPT = FALSE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "Key performs encryption only when ENCRYPT = TRUE");
            }
        }
    }

    /**
     * Basic test for Decrypt.
     */
    private void testDecrypt() throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing Decrypt.");
        }
        byte[] dataToEncrypt = BigInteger.TEN.toByteArray();
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] encryptedData = SK.encrypt(dataToEncrypt);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK.decrypt(encryptedData);
                logger.addFailureWithComment(true, "Key performs decryption only when DECRYPT = TRUE", 3, "Decryption is possible even when the key has DECRYPT = FALSE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Key performs decryption only when DECRYPT = TRUE");
            }
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                prepareKey(PuK, templateMaker.getBasicRSAPublicKeyTemplate(true, false), templateMaker.getBasicRSAPrivateKeyTemplate(true, true, false, false));
                byte[] encryptedData = PuK.encrypt(dataToEncrypt);
                PrK.decrypt(dataToEncrypt);
                logger.addFailureWithComment(false, "Key performs decryption only when DECRYPT = TRUE", 3, "Decryption is possible even when the key has DECRYPT = FALSE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "Key performs decryption only when DECRYPT = TRUE");
            }
        }
    }

    /**
     * Goes through the tests of known attacks.
     */
    private void testKnownAttacks() {
        try {
            testKeySeparation();
            testExpandedKeySeparation();
            testDowngradeAttack();
            testKeyBindingAttack();
            testWeakerAlgorithmAttack();
            testKeyDerivationAttacks();
            testPrivateKeyModificationAttack();
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(Session.class.getName()).log(Level.SEVERE, null, ex);
            ex.printStackTrace();
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(Session.class.getName()).log(Level.SEVERE, null, ex);
            ex.printStackTrace();
        }
    }

    /**
     * Tests the basic key separation. This is a subset of the expanded version,
     * but critical nonetheless.
     */
    private void testKeySeparation() throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing Key Separation Attack.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_DECRYPT, true));
                logger.addFailureWithComment(true, "Key separation attack - it is not possible to set both WRAP and DECRYPT", 3, "It is possible to set DECRYPT to TRUE when WRAP = TRUE.");
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_WRAPPING_KEY_CAN_DECRYPT, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Key separation attack - it is not possible to set both WRAP and DECRYPT (setting DECRYPT)");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                SK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_WRAP, true));
                logger.addFailureWithComment(true, "Key separation attack - it is not possible to set both WRAP and DECRYPT", 3, "It is possible to set WRAP to TRUE when DECRYPT = TRUE.");
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_WRAPPING_KEY_CAN_DECRYPT, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Key separation attack - it is not possible to set both WRAP and DECRYPT (setting WRAP)");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, false, true, true, true, true);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(template);
                logger.addFailureWithComment(true, "Key separation attack - it is not possible create key (GenerateKey) with both WRAP and DECRYPT set", 3, "It is possible to create a key with both WRAP and DECRYPT = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Key separation attack - it is not possible create key (GenerateKey) with both WRAP and DECRYPT set");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                SK.create(null);
                logger.addFailureWithComment(true, "Key separation attack - it is not possible create key (CreateKey) with both WRAP and DECRYPT set", 3, "It is possible to create a key with both WRAP and DECRYPT = TRUE.");
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_CREATED_KEY_CAN_DECRYPT, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Key separation attack - it is not possible create key (CreateKey) with both WRAP and DECRYPT set");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                Template copyTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_WRAP, true));
                copyTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_DECRYPT, true));
                SK.copyObject(copyTemplate);
                logger.addFailureWithComment(true, "Key separation attack - it is not possible create key (CopyObject) with both WRAP and DECRYPT set", 3, "It is possible to create a key with both WRAP and DECRYPT = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Key separation attack - it is not possible create key (CopyObject) with both WRAP and DECRYPT set");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = SK.wrapKey(LSK);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_DECRYPT, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_WRAP, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                logger.addFailureWithComment(true, "Expanded key separation attack - it is not possible create key (UnwrapKey) with WRAP and DECRYPT set", 3, "Unwrapping works even when WRAP and DECRYPT are set to TRUE in the template.");
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_CREATED_KEY_CAN_DECRYPT, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception pex) {
                logger.addSuccess(true, "Key separation attack - it is not possible create key (UnwrapKey) with WRAP and DECRYPT set");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                Template deriveTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_WRAP, true));
                deriveTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_DECRYPT, true));
                deriveTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                deriveTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 24));
                CK_MECHANISM deriveMechanism = new CK_MECHANISM(PKCS11Constants.CKM_EXTRACT_KEY_FROM_KEY, (long) 0);
                SK.deriveKey(deriveMechanism, deriveTemplate);
                logger.addFailureWithComment(true, "Key separation attack - it is not possible create key (DeriveKey) with both WRAP and DECRYPT set", 3, "It is possible to create a key with both WRAP and DECRYPT = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Key separation attack - it is not possible create key (DeriveKey) with both WRAP and DECRYPT set");
            }
        }
        //asymmetric section
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                prepareKey(PuK, templateMaker.getRSAPublicKeyGenerationTemplate(new BigInteger("257").toByteArray(), 512, true, true), templateMaker.getRSAPrivateKeyGenerationTemplate(true, true, false, false));
                PrK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_DECRYPT, true));
                logger.addFailureWithComment(false, "Key separation attack - it is not possible to set both WRAP and DECRYPT", 3, "It is possible to set DECRYPT to TRUE when WRAP = TRUE.");
                tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_WRAPPING_KEY_CAN_DECRYPT, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "Key separation attack - it is not possible to set both WRAP and DECRYPT (setting DECRYPT)");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                prepareKey(PuK, templateMaker.getRSAPublicKeyGenerationTemplate(new BigInteger("257").toByteArray(), 512, true, false), templateMaker.getRSAPrivateKeyGenerationTemplate(true, true, true, false));
                PuK.setAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_WRAP, true));
                logger.addFailureWithComment(false, "Key separation attack - it is not possible to set both WRAP and DECRYPT", 3, "It is possible to set WRAP to TRUE when DECRYPT = TRUE.");
                tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_WRAPPING_KEY_CAN_DECRYPT, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "Key separation attack - it is not possible to set both WRAP and DECRYPT (setting WRAP)");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.generateKeyPair(null, null);
                logger.addFailureWithComment(false, "Key separation attack - it is not possible create key (GenerateKey) with both WRAP and DECRYPT set", 3, "It is possible to create a key with both WRAP and DECRYPT = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "Key separation attack - it is not possible create key (GenerateKey) with both WRAP and DECRYPT set");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.createKeyPair(null, null);
                logger.addFailureWithComment(false, "Key separation attack - it is not possible create key (CreateKey) with both WRAP and DECRYPT set", 3, "It is possible to create a key with both WRAP and DECRYPT = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "Key separation attack - it is not possible create key (CreateKey) with both WRAP and DECRYPT set");
            }
        }
    }

    private void testExpandedKeySeparation() throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing Expanded Key Separation Attack.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template generationTemplate = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, true, true, true, true, true);
                generationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(generationTemplate);
                logger.addFailureWithComment(true, "Expanded key separation attack - it is not possible to combine WRAP and DECRYPT in one key", 3, "A generated key can have all ENCRYPT, DECRYPT, WRAP and UNWRAP set to TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Expanded key separation attack - it is not possible to combine WRAP and DECRYPT in one key");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template generationTemplate = templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher());
                generationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(generationTemplate);
                logger.addSuccess(true, "Expanded key separation attack - key with only WRAP + UNWRAP works.");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Expanded key separation attack - key with only WRAP + UNWRAP works.", 3, "It is not possible to generate a key that only has WRAP + UNWRAP. " + ex.getMessage());
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template generationTemplate = templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher());
                generationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 32));
                SK.generate(generationTemplate);
                logger.addSuccess(true, "Expanded key separation attack - key with only ENCRYPT + DECRYPT works.");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(true, "Expanded key separation attack - key with only ENCRYPT + DECRYPT works.", 3, "It is not possible to generate a key that only has ENCRYPT + DECRYPT. " + ex.getMessage());
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher());
                byte[] randomValue = new byte[24]; //we can safely use 24-byte long keys, since both DES3 and AES permit 24 byte long keys.
                random.nextBytes(randomValue);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE, randomValue));
                SK.create(template);
                logger.addFailureWithComment(true, "Expanded key separation attack - it is not possible create key (CreateKey) with WRAP set", 3, "It is possible to create a key with either WRAP = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Expanded key separation attack - it is not possible create key (CreateKey) with WRAP set");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher());
                byte[] randomValue = new byte[24]; //we can safely use 24-byte long keys, since both DES3 and AES permit 24 byte long keys.
                random.nextBytes(randomValue);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE, randomValue));
                SK.create(template);
                logger.addFailureWithComment(true, "Expanded key separation attack - it is not possible create key (CreateKey) with DECRYPT set", 3, "It is possible to create a key with either DECRYPT = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Expanded key separation attack - it is not possible create key (CreateKey) with DECRYPT set");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                Template deriveTemplate = new Template();
                deriveTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_WRAP, true));
                deriveTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                deriveTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 24));
                CK_MECHANISM deriveMechanism = new CK_MECHANISM(PKCS11Constants.CKM_EXTRACT_KEY_FROM_KEY, (long) 0);
                SK.deriveKey(deriveMechanism, deriveTemplate);
                logger.addFailureWithComment(true, "Expanded key separation attack - it is not possible create key (DeriveKey) with WRAP set", 3, "It is possible to create a key with both WRAP = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Expanded key separation attack - it is not possible create key (DeriveKey) with WRAP set");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyEncryptDecryptTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                Template deriveTemplate = new Template();
                deriveTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_DECRYPT, true));
                deriveTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                deriveTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 24));
                CK_MECHANISM deriveMechanism = new CK_MECHANISM(PKCS11Constants.CKM_EXTRACT_KEY_FROM_KEY, (long) 0);
                SK.deriveKey(deriveMechanism, deriveTemplate);
                logger.addFailureWithComment(true, "Expanded key separation attack - it is not possible create key (DeriveKey) with DECRYPT set", 3, "It is possible to create a key with both DECRYPT = TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Expanded key separation attack - it is not possible create key (DeriveKey) with DECRYPT set");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = SK.wrapKey(LSK);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_DECRYPT, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                logger.addFailureWithComment(true, "Expanded key separation attack - it is not possible create key (UnwrapKey) with DECRYPT set", 3, "Unwrapping works even when DECRYPT is set to TRUE in the template.");
            } catch (PKCS11Exception pex) {
                logger.addSuccess(true, "Expanded key separation attack - it is not possible create key (UnwrapKey) with DECRYPT set");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = SK.wrapKey(LSK);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_DECRYPT, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                logger.addFailureWithComment(true, "Expanded key separation attack - it is not possible create key (UnwrapKey) with WRAP set", 3, "Unwrapping works even when WRAP is set to TRUE in the template.");
            } catch (PKCS11Exception pex) {
                logger.addSuccess(true, "Expanded key separation attack - it is not possible create key (UnwrapKey) with WRAP set");
            }
        }
        //asymmetric section
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.generateKeyPair(null, null);
                logger.addFailureWithComment(false, "Expanded key separation attack - it is not possible to combine WRAP and DECRYPT in one key pair", 3, "A generated key pair can have all ENCRYPT, DECRYPT, WRAP and UNWRAP set to TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(false, "Expanded key separation attack - it is not possible to combine WRAP and DECRYPT in one key pair");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.generateKeyPair(templateMaker.getRSAPublicKeyGenerationTemplate(new BigInteger("257").toByteArray(), 512, false, true), templateMaker.getRSAPrivateKeyGenerationTemplate(true, true, false, true));
                logger.addSuccess(false, "Expanded key separation attack - key pair with only WRAP + UNWRAP works.");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "Expanded key separation attack - key pair with only WRAP + UNWRAP works.", 3, "It is not possible to generate a key that only has WRAP + UNWRAP. " + ex.getMessage());
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                PuK.generateKeyPair(templateMaker.getRSAPublicKeyGenerationTemplate(new BigInteger("257").toByteArray(), 512, true, false), templateMaker.getRSAPrivateKeyGenerationTemplate(true, true, true, false));
                logger.addSuccess(false, "Expanded key separation attack - key pair with only ENCRYPT + DECRYPT works.");
            } catch (PKCS11Exception ex) {
                logger.addFailureWithComment(false, "Expanded key separation attack - key pair with only ENCRYPT + DECRYPT works.", 3, "It is not possible to generate a key that only has ENCRYPT + DECRYPT. " + ex.getMessage());
            }
        }
    }

    private void testDowngradeAttack() throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (options.isInform()) {
            System.out.println("Testing Downgrade Attack.");
        }
        //symmetric section
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = SK.wrapKey(LSK);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, false));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                logger.addFailureWithComment(true, "Downgrade attack - it is not possible to unwrap key with SENSITIVE = FALSE", 3, "Unwrapping incorrectly permits setting SENSITIVE of the newly unwrapped key to FALSE.");
            } catch (PKCS11Exception pex) {
                logger.addSuccess(true, "Downgrade attack - it is not possible to unwrap key with SENSITIVE = FALSE");
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = SK.wrapKey(LSK);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                ISK = SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                if (ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE))) {
                    logger.addSuccess(true, "Downgrade attack - unwrapped key is set to SENSITIVE = TRUE by default");
                } else {
                    logger.addFailureWithComment(true, "Downgrade attack - unwrapped key is set to SENSITIVE = TRUE by default", 3, "The default SENSITIVE for a new key is FALSE.");
                }
            } catch (PKCS11Exception pex) {
                logger.addFailureWithComment(true, "Downgrade attack - unwrapped key is set to SENSITIVE = TRUE by default", 3, "Unwrap failed when it should not " + pex.getMessage());
            }
        }
        //asymmetric section
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY) && tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                prepareKey(PuK, templateMaker.getRSAPublicKeyGenerationTemplate(new BigInteger("257").toByteArray(), 512, true, true), templateMaker.getRSAPrivateKeyGenerationTemplate(true, true, true, true));
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = PuK.wrapKey(LSK);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, false));
                PrK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                logger.addFailureWithComment(false, "Downgrade attack - it is not possible to unwrap key with SENSITIVE = FALSE", 3, "Unwrapping incorrectly permits setting SENSITIVE of the newly unwrapped key to FALSE.");
            } catch (PKCS11Exception pex) {
                logger.addSuccess(false, "Downgrade attack - it is not possible to unwrap key with SENSITIVE = FALSE");
            }
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                prepareKey(PuK, templateMaker.getRSAPublicKeyGenerationTemplate(new BigInteger("257").toByteArray(), 512, true, true), templateMaker.getRSAPrivateKeyGenerationTemplate(true, true, true, true));
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = PuK.wrapKey(LSK);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                ISK = PrK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                if (ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE))) {
                    logger.addSuccess(false, "Downgrade attack - unwrapped key is set to SENSITIVE = TRUE by default");
                } else {
                    logger.addFailureWithComment(false, "Downgrade attack - unwrapped key is set to SENSITIVE = TRUE by default", 3, "The default SENSITIVE for a new key is FALSE.");
                }
            } catch (PKCS11Exception pex) {
                logger.addFailureWithComment(false, "Downgrade attack - unwrapped key is set to SENSITIVE = TRUE by default", 3, "Unwrap failed when it should not " + pex.getMessage());
            }
        }
    }

    private void testKeyBindingAttack() {
        if (options.isInform()) {
            System.out.println("Testing Key Binding Attack.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, CipherEnum.DES3);
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = LSK.wrapKey(SK);
                byte[] firstHalf = new byte[wrappedKey.length / 2];
                byte[] secondHalf = new byte[wrappedKey.length / 2];
                for (int i = 0; i < firstHalf.length; i++) {
                    firstHalf[i] = wrappedKey[i];
                }
                int offset = wrappedKey.length / 2;
                for (int i = 0; i < secondHalf.length; i++) {
                    secondHalf[i] = wrappedKey[i + offset];
                }
                //manual unwrap is necessary here
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_DES));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                pkcs11.C_UnwrapKey(sessionHandle, new CK_MECHANISM(LSK.getCipherEnum().getCipherMechanism()), LSK.getKeyHandle(), firstHalf, template.finishAndGetAttributes());
                logger.addFailureWithComment(false, "Key binding attack is not possible.", 2, "It is possible to unwrap first and second half of a DES3 key independently, which makes Key Binding attack possible.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Key binding attack is not possible.");
            }
        }
    }

    private void testWeakerAlgorithmAttack() {
        if (options.isInform()) {
            System.out.println("Testing Weaker Algorithm Attack.");
        }
        //so far only RC2 is tested, manual key operations may be necessary
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, CipherEnum.RC2);
                LSK.generate(null);
                LSK.wrapKey(SK);
                logger.addFailureWithComment(false, "Weaker algorithm attack (RC2) is not possible.", 2, "RC2 can be used to wrap AES or DES3 keys, which makes Weaker Algorithm Attack possible.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Weaker algorithm attack (RC2) is not possible. " + ex.getMessage());
            }
        }
    }

    private void testKeyDerivationAttacks() {
        if (options.isInform()) {
            System.out.println("Testing Key Derivation Attack.");
        }
        //cryptographic strength currently not tested, because it crashes the tokens..
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {//testing SENSITIVE setting with key extraction
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, true));
                prepareKey(SK, template, null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_EXTRACT_KEY_FROM_KEY, (long) 0); //this is a gambit that could potentially lead to catastrophic disaster, but seemingly works..
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 24));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, false));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                logger.addFailureWithComment(true, "Derivation (key extraction) cannot set SENSITIVE to FALSE", 3, "SENSITIVE can be set to FALSE in the derivation template, even though the original key had SENSITIVE set to TRUE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Derivation (key extraction) cannot set SENSITIVE to FALSE");
            }
            try {//testing EXTRACTABLE setting with key extraction
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_DERIVE, true));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, false));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_SENSITIVE, true));
                prepareKey(SK, template, null);
                CK_MECHANISM derivationMechanism = new CK_MECHANISM(PKCS11Constants.CKM_EXTRACT_KEY_FROM_KEY, (long) 0); //this is a gambit that could potentially lead to catastrophic disaster, but seemingly works..
                Template derivationTemplate = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, "meaninsgless"));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, PKCS11Constants.CKK_GENERIC_SECRET));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE_LEN, 24));
                derivationTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_EXTRACTABLE, true));
                ISK = (SecretKey) SK.deriveKey(derivationMechanism, derivationTemplate);
                logger.addFailureWithComment(true, "Derivation (key extraction) cannot set EXTRACTABLE to TRUE", 3, "EXTRACTABLE can be set to TRUE in the derivation template, even though the original key had EXTRACTABLE set to FALSE.");
            } catch (PKCS11Exception ex) {
                logger.addSuccess(true, "Derivation (key extraction) cannot set EXTRACTABLE to TRUE");
            }
        }
    }

    private void testPrivateKeyModificationAttack() {
        if (options.isInform()) {
            System.out.println("Testing Private Key Modification Attack.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY) && tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                prepareKey(PuK, null, null);
                byte[] wrappedKey = SK.wrapKey(PrK);
                for (int i = 0; i < wrappedKey.length; i++) {
                    System.out.println(wrappedKey[i]);
                }
                logger.addFailureWithComment(false, "Private key modification attack is not possible - importing altered private key", 3, "Private key that got changed outside of the token was successfully imported back to the device.");
            } catch (PKCS11Exception ex) {
                if (ex.getMessage().contains("CKR_DATA_LEN_RANGE")) {
                    logger.addFailureWithComment(false, "Private key modification attack is not possible - importing altered private key", 3, "Wrapping of private key by a secret key failed, test could not finish: " + ex.getMessage());
                } else {
                    logger.addSuccess(false, "Private key modification attack is not possible - importing altered private key");
                }
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = SK.wrapKey(LSK);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                ISK = SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                if (!ISK.attributeIsTrue(new CK_ATTRIBUTE(PKCS11Constants.CKA_WRAP))) {
                    logger.addSuccess(false, "Private key modification attack - WRAP of unwrapped key is FALSE by default.");
                } else {
                    logger.addFailureWithComment(false, "Private key modification attack - WRAP of unwrapped key is FALSE by default.", 3, "An unwrapped key has WRAP set to TRUE by default.");
                }
            } catch (PKCS11Exception pex) {
                logger.addFailureWithComment(false, "Private key modification attack - WRAP of unwrapped key is FALSE by default.", 3, "Test failed: " + pex.getMessage());
            }
            try {
                SK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(SK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                LSK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                prepareKey(LSK, templateMaker.getSecretKeyWrapUnwrapTemplate(tokenCapabilities.getWorkingSymmetricCipher()), null);
                byte[] wrappedKey = SK.wrapKey(LSK);
                Template template = new Template(new CK_ATTRIBUTE(PKCS11Constants.CKA_KEY_TYPE, tokenCapabilities.getWorkingSymmetricCipher().getKeyType()));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_SECRET_KEY));
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_WRAP, true));
                ISK = SK.unwrapSecretKey(wrappedKey, template, tokenCapabilities.getWorkingSymmetricCipher());
                logger.addFailureWithComment(false, "Private key modification attack - WRAP of unwrapped key cannot be set in the unwrap template.", 3, "It is possible to set WRAP of newly unwrapped key to TRUE.");
            } catch (PKCS11Exception pex) {
                logger.addSuccess(false, "Private key modification attack - WRAP of unwrapped key cannot be set in the unwrap template.");
            }
        }
    }

    /**
     * Only includes tests that were not performed in other parts of the thesis
     * in order to avoid unnecessary code overhead
     */
    private void testForModelChecker() {
        if (options.isInform()) {
            System.out.println("Testing additional values for model checker.");
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.SYMMETRIC_CRYPTOGRAPHY)) {
            try {
                ISK = new SecretKey(sessionHandle, pkcs11, tokenCapabilities.getWorkingSymmetricCipher());
                Template template = templateMaker.getSecretKeyTemplate(tokenCapabilities.getWorkingSymmetricCipher(), true, true, false, false, false, true);
                byte[] randomValue = new byte[24]; //we can safely use 24-byte long keys, since both DES3 and AES permit 24 byte long keys.
                random.nextBytes(randomValue);
                template.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE, randomValue));
                ISK.create(template);
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_CREATED_KEY_CAN_UNWRAP, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                tokenCapabilities.addCapability(TokenCapability.SYMMETRIC_CREATED_KEY_CAN_UNWRAP, TokenCapabilityValue.DOES_NOT_WORK);
            }
        }
        if (tokenCapabilities.capabilityGroupWorking(TokenCapabilityGroup.ASYMMETRIC_CRYPTOGRAPHY)) {
            try {
                PrK = new PrivateKey(sessionHandle, pkcs11, CipherEnum.RSA);
                PuK = new PublicKey(sessionHandle, pkcs11, CipherEnum.RSA, PrK);
                Template privTemplate = templateMaker.getRSAPrivateKeyGenerationTemplate(false, false, false, false);
                privTemplate.addAttribute(new CK_ATTRIBUTE(PKCS11Constants.CKA_MODULUS_BITS, 512));
                Template pubTemplate = templateMaker.getRSAPublicKeyGenerationTemplate(new BigInteger("257").toByteArray(), 512, false, true);
                PuK.generateKeyPair(pubTemplate, privTemplate);
                tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_WRAPPING_KEY_CAN_WRAP, TokenCapabilityValue.WORKS_ACCORDINGLY);
            } catch (PKCS11Exception ex) {
                tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_WRAPPING_KEY_CAN_WRAP, TokenCapabilityValue.DOES_NOT_WORK);
//            Logger.getLogger(Session.class.getName()).log(Level.SEVERE, null, ex);
            } catch (NoSuchAlgorithmException ex) {
                tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_WRAPPING_KEY_CAN_WRAP, TokenCapabilityValue.DOES_NOT_WORK);
                Logger.getLogger(Session.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InvalidKeySpecException ex) {
                tokenCapabilities.addCapability(TokenCapability.ASYMMETRIC_WRAPPING_KEY_CAN_WRAP, TokenCapabilityValue.DOES_NOT_WORK);
                Logger.getLogger(Session.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private void prepareModelChecking(boolean separate) {
        ModelMaker converter = new ModelMaker(tokenCapabilities);
        String model = converter.prepareModel();
        try {
            if (separate) {
                PrintWriter separateWriter = new PrintWriter("preparedModelFile.hlpsl");
                separateWriter.print(model);
                separateWriter.close();
                System.out.println("HLPSL specification saved to preparedModelFile.hlpsl");
            }
            PrintWriter writer = new PrintWriter("avispa-1.1/pkcs11/modelFile.hlpsl");
            writer.print(model);
            writer.close();
        } catch (FileNotFoundException exception) {
            System.err.println("Preparation of model for checking failed: ");
//            exception.printStackTrace();
        }
    }
}
